const Koa = require("koa");
const path = require("path");
const logger = require("koa-logger");//产生日志记录
const onerror = require("koa-onerror");//错误显示转换
const serve = require("koa-static");
const { koaBody } = require("koa-body");
const router = require("./router");
const jwt = require("koa-jwt");
const token = require("jsonwebtoken");
const { historyApiFallback } = require('koa2-connect-history-api-fallback');

const app = new Koa();

app.use(logger());//日志：记录开发中所有的操作，可以快速错误定位以及触发错误的源头和执行过程
onerror(app);//错误显示转换

// vue-router历史模式兼容服务插件必须定义在所有服务器静态资源之前
//      服务器的请求资源不是只有vue项目一个，还包含其它静态资源和数据接口等
//      历史模式的兼容插件必须准确设置 vue 项目的入口html文件，和被操作的项目目录
//      并通过白名单方式完成其它非vue项目的路径放行
app.use( historyApiFallback({
    // index 用于配置 vue 项目的入口html文件
    // index的路径是以服务器路径为参考的 => http://127.0.0.1:3000/new/index.html
    index:"/new/index.html",
    // 访问路径判断重写，定义放行白名单
    rewrites:[
        {
            // 路径判断依据
            // 通过正则拦截符合条件的请求，经过to配置进行地址转换
            from:new RegExp("^(?!(/new/)).*"), 
            // 重写返回路径
            to:function(content){
                // 将访问路径直接向下进行服务器执行
                return content.parsedUrl.path
            }
        }
    ]
}) );

/* 静态资源处理中间件(图片、视频、css、js...),不需要服务器进行处理可以直接访问的资源
    koa-static可以在koa请求进行处理时候，判断浏览器访问的相关资源在对应的静态资源目录中是否存在
    如果存在则直接返回该静态资源的相关数据，且中间件不再调用next函数
    如果不存在则直接调用next函数访问下一个中间件
    在定义时候，需要指定一个项目目录作为静态资源的存放目录
    指定的静态存放目录，从项目结构上来说，可以认为是服务的根目录 */
// app.use(serve("./public/"));
let staticPath = path.join(__dirname, "./public/");
app.use(serve(staticPath));

// 文件上传中间件
app.use(koaBody({
    multipart: true,//支持文件上传
    formidable: {
        uploadDir: path.join(__dirname, "./public/upload"),//设置上传文件在服务器所存放的目录,文件存储时候会重命名操作
        keepExtensions: true//上传文件是否保留文件类型的后缀名
    }
}));

app.use(async function (ctx, next) {
    if(ctx.path.startsWith("/proxy")){
        await next()
    }else{
        // ctx.set("需要补充的响应头","允许跨域的服务器");
        ctx.set("Access-Control-Allow-Origin", "*");
        // HTTP复杂请求
        // 允许跨域请求方法的类型
        ctx.set("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE,PATCH");
        //允许跨域的请求头的值
        ctx.set("Access-Control-Allow-Headers", "Content-Type,Authorization,bb");
        //统一处理的测试请求
        if (ctx.method == "OPTIONS") {
            ctx.body = "";
        } else {
            await next();
        }
    }
});

// token授权
let tokenConfig = {
    sign: token.sign,
    secret: "itany secret"
};

// 授权校验
app.use(async function (ctx, next) {
    try {
        ctx.token = tokenConfig;
        await next();
    } catch (error) {
        if (error.status == 401) {
            ctx.body = {
                state: false,
                msg: "未授权"
            }
        } else {
            throw error;//抛出错误，不对错误进行任何处理
        }

    }
});

// jwt({secret:"itany secret"}判断授权
// .unless({path:[正则]})定义不需要判断的路由地址
app.use(jwt({ secret: "itany secret" }).unless({ path: ["/",/^\/api\//,/^\/proxy\//,/^\/starPath\//] }));

/* 
router是路由对象，用于提供定义时地址和回调函数之间的对应关系
router完成关系定义后，由router.routes()方法执行完成路由表的生成
路由表-->一段js的路由和方法对应的判断逻辑 */
app.use(router.apiRouter.routes());
app.use(router.proxyRouter.routes());
app.use(router.userRouter.routes());


app.listen(3000);